# coding: UTF-8
"""
在大型项目中，经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构，使其更容易扩展。
在本节中，我们将创建一个名为game_functions的新模块，它将存储大量让游戏《外星人入侵》运行的函数。
通过创建模块game_functions，可避免alien_invasion.py太长，并使其逻辑更容易理解。
"""
# 【标准库与第三方库】
import sys
import pygame
from time import sleep
# 【自定义模块】
from weapon import Bullet
from enemy import Enemy


def fire_bullets(FP_settings, FP_screen, FP_ship, FP_bullets_group) -> None:
    """【函 数】机炮开火（发射子弹）
    """
    if (len(FP_bullets_group) <= FP_settings.bullet_allowed):   # 检查子弹数量限制
        # 创建一颗子弹，并加入到编组中
        new_bullet = Bullet(FP_settings, FP_screen, FP_ship)
        FP_bullets_group.add(new_bullet)
    return None


def check_KEYDOWN_event(FP_event, FP_settings, FP_stats, FP_screen, FP_ship, FP_bullets_group) -> None:
    """【函 数】响应按下键盘
    """
    if (FP_event.key == pygame.K_RIGHT):
        FP_ship.ATR_moving_right = True
    elif (FP_event.key == pygame.K_LEFT):
        FP_ship.ATR_moving_left = True
    elif (FP_event.key == pygame.K_UP):
        FP_ship.ATR_moving_up = True
    elif (FP_event.key == pygame.K_DOWN):
        FP_ship.ATR_moving_down = True
    elif (FP_event.key == pygame.K_SPACE):
        fire_bullets(FP_settings, FP_screen, FP_ship, FP_bullets_group)
    elif (FP_event.key == pygame.K_KP_ENTER):
        game_activate(FP_stats)   # 按Enter开始游戏（有BUG）
    elif (FP_event.key == pygame.K_ESCAPE):
        game_deactivate(FP_stats)   # 按ESC暂停游戏（实际是重置）
    elif (FP_event.key == pygame.K_q):
        sys.exit()
    return None


def check_KEYUP_event(FP_event, FP_ship) -> None:
    """【函 数】响应松开键盘
    """
    if (FP_event.key == pygame.K_RIGHT):
        FP_ship.ATR_moving_right = False
    elif (FP_event.key == pygame.K_LEFT):
        FP_ship.ATR_moving_left = False
    elif (FP_event.key == pygame.K_UP):
        FP_ship.ATR_moving_up = False
    elif (FP_event.key == pygame.K_DOWN):
        FP_ship.ATR_moving_down = False
    return None


def check_event(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_bullets_group, FP_enemy_group, FP_play_button) -> None:
    """【函 数】响应键盘和鼠标事件
    """
    for LV_event in pygame.event.get():
        if (LV_event.type == pygame.QUIT):
            sys.exit()
        elif (LV_event.type == pygame.MOUSEBUTTONDOWN):
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_enemy_group, FP_bullets_group, FP_play_button, mouse_x, mouse_y)
        elif (LV_event.type == pygame.KEYDOWN):
            check_KEYDOWN_event(LV_event, FP_settings, FP_stats, FP_screen, FP_ship, FP_bullets_group)
        elif (LV_event.type == pygame.KEYUP):
            check_KEYUP_event(LV_event, FP_ship)
    return None


def check_play_button(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_enemy_group, FP_bullets_group, FP_play_button, mouse_x, mouse_y) -> None:
    """【函 数】单机“Play”按钮时开始新游戏
    """
    button_clicked = FP_play_button.rect.collidepoint(mouse_x, mouse_y)
    if (button_clicked and not FP_stats.ATR_game_active):
        # 重置游戏速度
        FP_settings.init_dynamic_settings()
        # 游戏开始后隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        FP_stats.reset_stats()
        FP_stats.ATR_game_active = True
        # 重置记分信息
        FP_score.prep_now_score()
        FP_score.prep_top_score()
        FP_score.prep_level()
        FP_score.prep_ships()
        # 清空敌机编队与子弹编组
        FP_enemy_group.empty()
        FP_bullets_group.empty()
        # 创建新的敌机编队，并让飞船居中
        create_enemy_fleet(FP_settings, FP_screen, FP_ship, FP_enemy_group)
        FP_ship.center_ship()
    return None


def check_bullet_hit_enemy(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_bullets_group, FP_enemy_group) -> None:
    """【函 数】响应子弹击中敌机
    """
    # 检查是否有子弹击中敌机，如果击中则删除对应的敌机
    collisions = pygame.sprite.groupcollide(FP_bullets_group, FP_enemy_group, True, True)
    if (collisions):
        # FP_stats.score += FP_settings.enemy_point # 若子弹同时击中两架敌机，只会记录为一架的得分，因此改为以下代码
        for enemys_hit in collisions.values():
            FP_stats.score += FP_settings.enemy_point * len(enemys_hit)
            FP_score.prep_now_score()
        check_high_score(FP_stats, FP_score)
    # 如果当前敌机编队中的敌机都被击落，则生成新的敌机
    if (len(FP_enemy_group) == 0):
        FP_bullets_group.empty()
        FP_settings.increase_speed()    # 提高游戏速度
        # 提高等级
        FP_stats.level += 1
        FP_score.prep_level()
        # 创建新的敌机编队
        create_enemy_fleet(FP_settings, FP_screen, FP_ship, FP_enemy_group)
    return None


def update_bullet(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_bullets_group, FP_enemy_group) -> None:
    """【函 数】更新子弹
    """
    # 更新子弹位置
    FP_bullets_group.update()
    # 删除超出屏幕范围的子弹
    for LV_bullet in FP_bullets_group.copy():
        if (LV_bullet.rect.bottom <= 0):
            FP_bullets_group.remove(LV_bullet)
    # print(len(FP_bullets_group))   # 查看子弹数量（测试代码，仅当调试时取消注释）
    # 检查子弹是否击中敌机
    check_bullet_hit_enemy(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_bullets_group, FP_enemy_group)
    return None


def get_enemy_number_x(FP_settings, FP_enemy_width) -> int:
    """【函 数】计算每行可容纳敌机的数量
    """
    VAR_available_space_x = FP_settings.screen_width - 2 * FP_enemy_width
    VAR_enemy_number_x = int(VAR_available_space_x / (2 * FP_enemy_width))
    return VAR_enemy_number_x


def get_enemy_number_y(FP_settings, FP_ship_height, FP_enemy_height) -> int:
    """【函 数】计算屏幕可容纳敌机的行数
    """
    VAR_available_space_y = FP_settings.screen_height - FP_ship_height -  3 * FP_enemy_height
    VAR_enemy_number_y = int(VAR_available_space_y / (2 * FP_enemy_height))
    return VAR_enemy_number_y


def create_enemy(FP_settings, FP_screen, FP_enemy_group, FP_row_number, FP_column_number) -> None:
    """【函 数】创建一只敌机并将其放在当前行
    """
    INS_enemy = Enemy(FP_settings, FP_screen)
    VAR_enemy_width = INS_enemy.rect.width
    INS_enemy.ATR_position_x = VAR_enemy_width + 2 * VAR_enemy_width * FP_column_number
    INS_enemy.rect.x = INS_enemy.ATR_position_x
    INS_enemy.rect.y = INS_enemy.rect.height + 2 * INS_enemy.rect.height * FP_row_number
    FP_enemy_group.add(INS_enemy)
    return None


def create_enemy_fleet(FP_settings, FP_screen, FP_ship, FP_enemy_group) -> None:
    """【函 数】创建敌机机群
    """
    INS_enemy = Enemy(FP_settings, FP_screen)   # 这里实例化“INS_enemy”并非为了创建敌机，而是需要用到“.rect.width”这一属性
    VAR_column_numbers = get_enemy_number_x(FP_settings, INS_enemy.rect.width)    # 获取每 行 能容纳的敌机数量
    VAR_row_numbers = get_enemy_number_y(FP_settings, FP_ship.rect.height, INS_enemy.rect.height)  #获取每 列 能容纳的敌机数量
    # 
    for LV_row_number in range(VAR_row_numbers):
        for LV_column_number in range(VAR_column_numbers):
            create_enemy(FP_settings, FP_screen, FP_enemy_group, LV_row_number, LV_column_number)
    return None


def change_fleet_direction(FP_settings, FP_enemy_group) -> None:
    """【函 数】将敌机编队下移，并改变它们的移动方向
    """
    for LV_enemy in FP_enemy_group.sprites():
        LV_enemy.rect.y += FP_settings.enemy_vertical_speed
    FP_settings.enemy_direction_factor *= -1
    return None


def check_fleet_edge(FP_settings, FP_enemy_group) -> None:
    """【函 数】敌机编队到达屏幕边缘时采取的措施
    """
    for LV_enemy in FP_enemy_group.sprites():
        if (LV_enemy.check_edge()):
            change_fleet_direction(FP_settings, FP_enemy_group)
            break
    return None


def ship_hit(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group) -> None:
    """【函 数】响应飞船与敌机相撞
    """
    if (FP_stats.ship_available > 0):
        # 将剩余飞船数量减 1 
        FP_stats.ship_available -= 1
        # 更新统计板中的剩余飞船图标数量
        FP_score.prep_ships()
        # 清空敌机编队与子弹编组
        FP_enemy_group.empty()
        FP_bullets_group.empty()
        # 创建新的敌机编队，并将飞船置于屏幕底端中央
        create_enemy_fleet(FP_settings, FP_screen, FP_ship, FP_enemy_group)
        FP_ship.center_ship()
        # 暂停游戏一小会
        sleep(1)
    else:
        game_deactivate(FP_stats)
    return None


def game_activate(FP_stats) -> None:
    """【函 数】
    """
    FP_stats.ATR_game_active = True
    pygame.mouse.set_visible(False)
    return None


def game_pause(FP_stats) -> None:
    """【函 数】暂停游戏
    """
    return None


def game_deactivate(FP_stats) -> None:
    """【函数】
    """
    FP_stats.ATR_game_active = False
    pygame.mouse.set_visible(True)
    return None


def check_enemy_bottom(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group) -> None:
    """【函 数】检查是否有敌机到达屏幕底部
    """
    screen_rect = FP_screen.get_rect()
    for LV_enemy in FP_enemy_group:
        if (LV_enemy.rect.bottom >= screen_rect.bottom):
            # 如果敌机编队到达底端，像发生碰撞做一样的处理
            ship_hit(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group)
            break
    return None


def update_enemy_group(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group) -> None:
    """【函 数】检查是否有敌机到达边缘，并改变机群的方向
    """
    # 检查是否有敌机到达边缘
    check_fleet_edge(FP_settings, FP_enemy_group)
    # 更新敌机编队
    FP_enemy_group.update()
    # 检测玩家飞船与敌机的碰撞
    if (pygame.sprite.spritecollideany(FP_ship, FP_enemy_group)):
        # print("Ship hit!!!")
        ship_hit(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group)
    # 检查是否有敌机到达屏幕底部
    check_enemy_bottom(FP_settings, FP_stats, FP_screen, FP_score, FP_ship, FP_bullets_group, FP_enemy_group)
    return None


def check_high_score(FP_stats, FP_score) -> None:
    """【函数】检查是否产生了更高的得分"""
    if (FP_stats.score > FP_stats.top_score):
        FP_stats.top_score = FP_stats.score
        FP_score.prep_top_score()
    return None


def update_screen(FP_settings, FP_screen, FP_stats, FP_score, FP_ship, FP_bullets_group, FP_enemy_group, FP_play_button) -> None:
    """【函 数】更新屏幕上的图像，并切换到新屏幕
    """
    # 每次循环时都重绘屏幕
    FP_screen.fill(FP_settings.background_color)   # 背景色
    # 绘制子弹
    for LV_bullet in FP_bullets_group.sprites():
        LV_bullet.MET_draw()
    # 绘制飞船
    FP_ship.MET_blit_ship()
    # FP_enemy.MET_blit_enemy()   # 绘制一只敌机
    # 绘制敌机舰队
    FP_enemy_group.draw(FP_screen)
    # 显示得分
    FP_score.show_score()
    # 如果游戏处于非活动状态，将显示“Play”按钮
    if (not FP_stats.ATR_game_active):
        FP_play_button.draw_button()
    # 让最近绘制的屏幕可见
    pygame.display.flip()
    return None
